Skip to content

Conversation

@dreamwasp
Copy link
Contributor

@dreamwasp dreamwasp commented Nov 7, 2025

Overview

Wraps focus when tabbing out of InfoTips for both inline + floating variants, extended

@aresnik11 wrote the functional code for the focus wrap, i just DRYed it up, added the tests + sanitized the screenreader text and made a few tweaks to global esc on inline tips as well

i also noticed the width was wrong on floating -right infotips, so i've fixed that. I also changed the examples to containerRefs since I'm going to change the onClick behavior in the following PR. The status in the InfoTip story has been updated to reflect that.

PR Checklist

  • Related to designs:
  • Related to JIRA ticket: GMT-1479
  • I have run this code to verify it works
  • This PR includes unit tests for the code change
  • This PR includes testing instructions tests for the code change
  • The alpha package of this PR is passing end-to-end tests in all relevant Codecademy repositories

Testing Instructions

  1. Go to the InfoTip stories in Storybook
  2. Navigate to the KeyboardNavigation story
  3. Use enter to open an InfoTip - verify focus moves to the Text container (you can tab from there to reach links)
  4. Test that Escape still closes the InfoTip and returns focus to the button
  5. Verify both floating and inline variants work correctly
  6. Run the InfoTip tests and verify all pass

PR Links and Envs

Repository PR Link
Monolith Monolith PR
Mono Mono PR

@nx-cloud
Copy link

nx-cloud bot commented Nov 7, 2025

View your CI Pipeline Execution ↗ for commit e078377


☁️ Nx Cloud last updated this comment at 2025-11-21 21:22:31 UTC

@dreamwasp dreamwasp marked this pull request as ready for review November 7, 2025 21:00
@dreamwasp dreamwasp requested a review from a team as a code owner November 7, 2025 21:00
@dreamwasp dreamwasp marked this pull request as draft November 11, 2025 14:36
@dreamwasp dreamwasp marked this pull request as ready for review November 12, 2025 20:40
@dreamwasp dreamwasp marked this pull request as draft November 12, 2025 20:41
@dreamwasp dreamwasp marked this pull request as ready for review November 12, 2025 21:01
Copy link
Contributor

@aresnik11 aresnik11 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for bringing this over the finish line!

@dreamwasp dreamwasp marked this pull request as draft November 17, 2025 20:46
@dreamwasp dreamwasp marked this pull request as ready for review November 19, 2025 20:44
Copy link
Contributor

@LinKCoding LinKCoding left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice changes to address the edge cases!
Left some thoughts, LMK if these would've been addressed in another PR, because none of my comments should be "blocking" per se. But maybe a "blocking" comment is with the "focus forward" behavior, I did have a question about the example, where the tabbing wasn't "contained" to the infotip after tabbing back to the infotip button.

Comment on lines 57 to 66
const getFocusableElements = useCallback(() => {
const popoverContent = popoverContentNodeRef.current;
if (!popoverContent) return [];

return Array.from(
popoverContent.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)
);
}, []);

const clearAndSetTimeout = useCallback(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: I wonder if this code/logic can be in utils?
seems like some external teams "need" more control over focus management, so maybe having it live in a more general file would help in the long run.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thought of how eventlisteners, or other helper functions can be abstracted elsewhere (and hopefully also re-used)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great idea - will move!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

love it, it is done


/*
* For floating placement, screenreader text comes before button to maintain
* correct DOM order despite Portal rendering. See GM-797 for planned fix.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is now GMT-64
see: https://skillsoftdev.atlassian.net/browse/GMT-64


### Floating Placement

When using `placement="floating"`, InfoTips implement **forward focus wrapping** to keep users within the tip context:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to define what forward focus wrapping is

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it meant like it'd be specific to focusing within the infotip link/buttons and back to the button itself and keep some kinda focus trap there.

but now I'm unsure because after we tab back to the button, then tabbing doesn't go back into the popover of the infotip.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i removed the focus trap so there is more natural tabbing, i'll reword!

<Canvas of={InfoTipStories.Placement} />

## InfoTips with links or buttons
## Keyboard Navigation & Accessibility
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## Keyboard Navigation & Accessibility
## Keyboard navigation & accessibility

Headings should be sentence case


<Canvas of={InfoTipStories.WithLinksOrButtons} />

### Floating Placement
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Floating Placement
### Floating placement

{...args}
info={
<Text>
<Text ref={ref} tabIndex={-1}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that the whole text gets focus when the infotip is clicked?
If so, it's a bit hard to see outright — maybe the 1st anchor can be the one that has focus? or the 2nd one to showcase that this programmatic focus is intentional?

And then the text can also change where it's like
"This info tip has a link but focus on clicking actually is set to 2nd link! Use the onClick prop!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah this is a change to focusing the container instead of the links first which is implemented in this PRhttps://github.com//pull/3215 - if you turn on VO you'll see the virtual cursor wrapping the entire text instead of the first link/button


<Box mt={16}>
<Text fontSize={14}>
<strong>Test Escape Key Behavior with Modals:</strong>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<strong>Test Escape Key Behavior with Modals:</strong>
<strong>Test Escape Key Behavior with Modals: </strong>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ cause there's no gap between this sentence and the next

Image

</Text>
<Box as="ul" fontSize={14} pl={16}>
<li>
<strong>Floating - Tab:</strong> Navigates forward through links,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious: Is this how we'd recommend making bold text?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if don't need any other styles just using the html semantics is perfectly viable

Comment on lines 240 to 252
1. Click the InfoTip to open it
<br />
2. Click &quot;Open Modal&quot; button
<br />
3. Press Escape - should close modal only (InfoTip stays open)
<br />
4. Press Escape again - should close InfoTip
<br />
<br />
<em>
InfoTip detects when modals are open and defers Escape key
handling to them.
</em>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can prob be simplified to a <ol> instead (without the numbering and the br

title="Test Modal"
onRequestClose={() => setIsModalOpen(false)}
>
<Box p={16}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small nit: FlexBox seems cleaner to me cause of the placement of the FillButton

@aresnik11 aresnik11 self-requested a review November 21, 2025 16:33
@codecademydev
Copy link
Collaborator

📬 Published Alpha Packages:

@codecademy/[email protected]
@codecademy/[email protected]
@codecademy/[email protected]

@github-actions
Copy link
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants